﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.IO;
using System.Text.RegularExpressions;
using SharpPcap;
using SharpPcap.Packets;
using Hids.Classes;

namespace Hids
{
    class Program
    {
        /// <summary>
        /// Holds the IPv4 Address of the device that is opened for listening
        /// </summary>
        public static string IpAddress;

        /// <summary>
        /// The main starting point for the program.  Execution begins here
        /// </summary>
        /// <param name="args">The arguments that were passed to the program</param>
        static void Main(string[] args)
        {
            int deviceNumber = -1;
            string deviceName = "";
            string attackSignaturesFile = "";//the default file for attack signatures
            List<string> filesToComputeHashes = new List<string>();
            List<string> registersToMonitor = new List<string>();

            #region Handle Input Arguments

            for (int i = 0; i < args.Length; i++)
            {
                switch(args[i])
                {
                    case "/?":
                    case "-h":
                    case "/h"://Print a help message
                        Console.WriteLine(
                            "Davjd Intrustion Detection System\n\n" +
                            "  Usage:  hips.exe -i interface [Options]\n" +
                            "\n" +
                            "Available Options:\n" +
                            "  -D                List all device names and number\n" +
                            "  -i interface      Set the interface to listen on by the name or number\n" + 
                            "  -s SMTP Server    Sets the smtp server to use when sending emails\n" +
                            "  -e Email          Sets the email address to send notfication to\n" + 
                            "  -f File           The XML file to load the attack signatures from\n" +  
                            "  -a File           The location of the hash file to use\n" + 
                            "  -l File           The location of a file with directory/file names in it\n" +
                            "                      that you want to hash.  One directory/file name per\n" +
                            "                      line.\n" +
                            "  -r File           The location of a file with the registry entries in it\n" +
                            "                      that you want monitored. One registry entry per line.\n" +
                            "\n" +
                            "Example usage:\n"+
                            "   hids.exe -i 0 -s sieve.esu.edu -e davjd@esu.edu -r register.txt -f attack_signatures.xml -l filesToHash.txt\n\n");
                        return;
                    case "-D"://print the devices to listen on and their associated numbers 
                        List<PcapDevice> devices = SharpPcap.Pcap.GetAllDevices();//get the devices to listen on
                        for (int j = 0; j < devices.Count; j++)
                            Console.WriteLine(j + ":  " + devices[j].Description);
                        return;
                    case "-i"://get the interface to listen on
                        i++;
                        if(i >= args.Length)
                        {
                            Console.WriteLine("No interface given");
                            return;
                        }
                        if (!int.TryParse(args[i], out deviceNumber))
                            deviceName = args[i];
                        continue;
                    case "-s"://provide an SMTP server to send email to
                        i++;
                        if(i >= args.Length)
                        {
                            Console.WriteLine("No SMTP serverd given");
                            return;
                        }
                        Notifications.SmtpServer = args[i];
                        continue;
                    case "-e"://provide an email to sent email to
                        i++;
                        if(i >= args.Length)
                        {
                            Console.WriteLine("No email address given");
                            return;
                        }
                        Regex emailTester = new Regex(@"[\W]"); //TODO: insert regular expression to test email addresses
                        if (emailTester.IsMatch(args[i]))
                            Notifications.RecipientEmails.Add(args[i]);
                        else
                        {
                            Console.WriteLine("Invalid email address provided:  " + args[i]);
                            return;
                        }
                        continue;
                    case "-f":
                        i++;
                        if (i >= args.Length)
                        {
                            Console.WriteLine("No file given");
                            return;
                        }
                        attackSignaturesFile = args[i];//set the attack signature file name
                        continue;
                    case "-a":
                        i++;
                        if (i >= args.Length)
                        {
                            Console.WriteLine("No hash file given");
                            return;
                        }
                        FileIntegrity.HashFileName = args[i];//set the hash file name
                        continue;
                    case "-l":
                        i++;
                        if (i >= args.Length)
                        {
                            Console.WriteLine("No input file for hashing given");
                            return;
                        }

                        StreamReader inputFile;
                        string contents;
                        try
                        {
                            inputFile = new StreamReader(args[i]);
                            contents = inputFile.ReadToEnd();
                        }
                        catch (Exception except)
                        {
                            Console.WriteLine("Incorrect input file format:  \n" + except.Message);
                            return;
                        }

                        string[] lines = contents.Split(new char[] { '\n' }, StringSplitOptions.RemoveEmptyEntries);
                        foreach (string line in lines)
                        {
                            string fileOrDirectoryName = line.Replace("\r", "");
                            if (File.Exists(fileOrDirectoryName))
                                filesToComputeHashes.Add(fileOrDirectoryName);
                            else if (Directory.Exists(fileOrDirectoryName))
                                filesToComputeHashes.AddRange(GetFilesInFolder(fileOrDirectoryName));
                            else
                            {
                                Console.WriteLine("File or Directory:  " + fileOrDirectoryName + " does not exist");
                                return;
                            }
                        }

                        continue;
                    case "-r":
                        i++;
                        if (i >= args.Length)
                        {
                            Console.WriteLine("No input file for registry monitoring given");
                            return;
                        }

                        StreamReader input;
                        string cont;
                        try
                        {
                            input = new StreamReader(args[i]);
                            cont = input.ReadToEnd();
                        }
                        catch (Exception except)
                        {
                            Console.WriteLine("Incorrect input file format:  \n" + except.Message);
                            return;
                        }
                        
                        string[] lin = cont.Split(new char[] { '\n' }, StringSplitOptions.RemoveEmptyEntries);
                        foreach (string line in lin)
                        {
                            registersToMonitor.Add(line.Replace("\r", ""));
                        }

                        continue;
                    default:
                        Console.WriteLine("Unknown argument: " + args[i]);
                        
                        return;
                }
            }

            #endregion

            #region Get the Network Device
            if (deviceName == "" && deviceNumber == -1)
            {//insure that a valid device name or number was specified
                Console.WriteLine("No network interface given, please specify a network interface using -i.\n");
                return;
            }
            PcapDevice device;
            if (deviceName != "")
                device = Pcap.GetPcapDevice(deviceName);//get the device from its name
            else
            {
                List<PcapDevice> devices = Pcap.GetAllDevices();
                if (devices.Count > deviceNumber && deviceNumber >= 0)
                    device = devices[deviceNumber];
                else
                {
                    Console.WriteLine("Incorrect network device number given, please ensure this number is correct using -D\n");
                    return;
                }
            }

            #endregion

            if (attackSignaturesFile != "" && !NetworkAnalyzer.LoadAttackSignatures(attackSignaturesFile))
                return;//if there is an error loading the attack signatures file, exit
    

            device.OnPacketArrival += new Pcap.PacketArrivalEvent(NetworkAnalyzer.PacketArrived_EventHandler);
            device.Open();//open the device
            device.StartCapture(); //start capturing packets and sending them to the Packet Arrived event handler

            #region Get the IPv4 address of the device

            foreach (Pcap.PcapAddress address in device.Addresses)
            {//find the IPv4 address of the device
                if (address.Addr.sa_family == 2)
                {
                    IpAddress = address.Addr.ipAddress.ToString();
                    break;
                }
            }
            Console.WriteLine("Listening on " + IpAddress);

            #endregion

            int k = 0;
            Console.WriteLine("Hit Enter to exit\n");
            while (!Console.KeyAvailable || Console.ReadKey(true).Key != ConsoleKey.Enter) //Enter the main loop
            {//Only check for enter if a key has been pressed
                System.Threading.Thread.Sleep(200);//wait a little so we don't use all of the system's resources
                
                NetworkAnalyzer.LookForUnansweredSynConnections(); //look for unanswered syn connections

                if (k % 5 == 0)//only run these once every 5 loops
                {
                    k = 0;
                    EventLogs.CheckSecurityLogs(); //Check the event logs for an attack
                    if (filesToComputeHashes.Count != 0)//if there are files to check the hashes, then check them
                        FileIntegrity.CompareHashes(filesToComputeHashes);
                    if (registersToMonitor.Count != 0)//if there are registry keys to check, then check them
                        RegistryIntegrity.CheckRegisters(registersToMonitor);
                }
                k++;
            }

            device.StopCapture();//stop capturing packets
            device.Close();//close the device
        }

        /// <summary>
        /// Recurisvely gets a list of all the files underneath a directory, including files in subdirectories
        /// </summary>
        /// <param name="directoryName">the directory to get the files underneath it</param>
        /// <returns>A list of all the files underneath a directory, including files in subdirectories</returns>
        private static List<string> GetFilesInFolder(string directoryName)
        {
            List<string> filesInDirectories = new List<string>(Directory.GetFiles(directoryName));//Get all files in the directory
            List<string> subDirectories = new List<string>(Directory.GetDirectories(directoryName));//Get all the subdirectories

            foreach (string subDirectory in subDirectories)
                filesInDirectories.AddRange(GetFilesInFolder(subDirectory));

            return filesInDirectories;
        }
    }
}
